Chapter 6 Data Visualizations

6.1 Room Temperature Reduction

6.1.1 Task

As part of an energy optimization, you lower the room temperatures in a room and would now like to show the reduction effect using the time series of the room temperature sensor. In the example below you make two optimizations at different dates.

You want to create a time series plot with

  • the daily median, min and max value

  • the overall median of each period

  • the desired setpoint

6.1.2 Basis

  • Time series data from e.g. a temperature sensor with unaligned time intervals

6.1.3 Solution

library(dplyr)
library(lubridate)
library(dygraphs)
library(xts)
library(redutils)
library(RColorBrewer)

# Settings
tempSetpoint = 22.0

startDate = "2018-11-01"
endDate = "2019-02-01"

optiDate1 = "2018-12-17"
optiLabel1 = "Optimization I"

optiDate2 = "2019-01-03"
optiLabel2 = "Optimization II"

optiDelayDays = 5

# read and print data
df <- read.csv("https://github.com/hslu-ige-laes/edar/raw/master/sampleData/flatTempHum.csv",
                 stringsAsFactors=FALSE,
                 sep =";")

# select temperature and remove empty cells
df <- df %>% select(time, FlatA_Temp) %>% na.omit()

# create column with day for later grouping
df$time <- parse_date_time(df$time, "YmdHMS", tz = "Europe/Zurich")
df$day <- as.Date(cut(df$time, breaks = "day"))
df$day <- as.Date(as.character(df$day,"%Y-%m-%d"))

# filter time range
df <- df %>% filter(day > startDate, day < endDate)

# calculate daily median, min and max of temperature
df <- df %>%
  group_by(day) %>%
  mutate(minDay = min(as.numeric(FlatA_Temp)),
         medianDay = median(as.numeric(FlatA_Temp)),
         maxDay = max(as.numeric(FlatA_Temp))
         ) %>%
  ungroup()

# shrink down to daily values and remove rows with empty values
df <- df %>% select(day, medianDay, minDay, maxDay) %>% unique() %>% na.omit()

# calculate medians for time ranges
df <- df %>%
  mutate(period = ifelse(day >= startDate & day <= optiDate1,
                         "Baseline",
                         ifelse((day >= (as.Date(optiDate1) + optiDelayDays))
                                & (day <= optiDate2),
                                "Opti1",
                                ifelse((day >= (as.Date(optiDate2) + optiDelayDays))
                                & (day <= endDate),
                                "Opti2",
                                NA)
                         )))

df <- df %>%
  group_by(period) %>%
  mutate(medianPeriod = ifelse(is.na(period), NA, median(medianDay))) %>% 
  ungroup() %>% 
  select(-period)

# create xts object for plotting
plotdata <- xts( x=df[,-1], order.by=df$day)

# plot graph
dygraph(plotdata, main = "Room Temperature Reduction") %>%
  dyAxis("x", drawGrid = FALSE) %>%
  dySeries(c("minDay", "medianDay", "maxDay"),
           label = "Temperature") %>%
  dySeries(c("medianPeriod"),
           label = "Median Period",
           strokePattern = "dashed") %>%
  dyOptions(colors = RColorBrewer::brewer.pal(3, "Set2")) %>%
  dyEvent(x = optiDate1,
          label = optiLabel1,
          labelLoc = "bottom",
          color = "slategray",
          strokePattern = "dotted") %>% 
  dyEvent(x = optiDate2,
          label = optiLabel2,
          labelLoc = "bottom",
          color = "slategray",
          strokePattern = "dotted") %>% 
  dyLimit(tempSetpoint,
          color = "red",
          label = "Target Setpoint") %>% 
  dyRangeSelector() %>% 
  dyLegend(show = "always")

6.1.4 Discussion

In this example we used the dygraph package to create the graph. This package is fast and allows to show a rangeslider on the bottom of the graph. The exact same graph but without a slider is as well possible with ggplot.

Please note that the calculation of the periodic median after optimization I and II starts delayed because it takes time until the building has cooled down.

6.2 Building Energy Signature

6.2.1 Task

You want to create a scatter plot with

  • the daily mean outside temperature on the x-axis

  • the daily energy consumption on the y-axis

  • points colored according to season

6.2.2 Basis

  • Two separate csv files with time series data from the outside temperature and the energy data with unaligned time intervals

  • Energy consumption time series from a energy meter with steadily increasing meter values

6.2.3 Solution

After reading in the two time series the data has to get aggregated per day and then merged. Note that during the aggregation of the energy data you have to calculate the daily conspumption from the steadiliy increasing meter values as well.

Create a new script, copy/paste the following code and run it:

library(ggplot2)
library(plotly)
library(dplyr)
library(redutils)
library(lubridate)

# load time series data and aggregate daily mean values
dfOutsideTemp <- read.csv("https://github.com/hslu-ige-laes/edar/raw/master/sampleData/centralOutsideTemp.csv",
                          stringsAsFactors=FALSE,
                          sep =";")

dfOutsideTemp$time <- parse_date_time(dfOutsideTemp$time,
                                      order = "YmdHMS",
                                      tz = "Europe/Zurich")

dfOutsideTemp$day <- as.Date(cut(dfOutsideTemp$time, breaks = "day"))

dfOutsideTemp <- dfOutsideTemp %>%
  group_by(day) %>%
  mutate(tempMean = mean(centralOutsideTemp)) %>%
  ungroup()

dfOutsideTemp <- dfOutsideTemp %>%
  select(day, tempMean) %>%
  unique() %>%
  na.omit()

dfHeatEnergy <- read.csv("https://github.com/hslu-ige-laes/edar/raw/master/sampleData/centralHeating.csv",
                         stringsAsFactors=FALSE,
                         sep =";")

dfHeatEnergy <- dfHeatEnergy %>%
  select(time, energyHeatingMeter) %>%
  na.omit()

dfHeatEnergy$time <- parse_date_time(dfHeatEnergy$time,
                                     orders = "YmdHMS",
                                     tz = "Europe/Zurich")

dfHeatEnergy$day <- as.Date(cut(dfHeatEnergy$time, breaks = "day"))

dfHeatEnergy <- dfHeatEnergy %>%
  group_by(day) %>%
  mutate(energyMax = max(energyHeatingMeter)) %>%
  ungroup()

dfHeatEnergy <- dfHeatEnergy %>%
  select(day, energyMax) %>%
  unique() %>%
  na.omit()

dfHeatEnergy <- dfHeatEnergy %>% 
  mutate(energyCons = energyMax - lag(energyMax)) %>%
  select(-energyMax) %>%
  na.omit()

# merge the data in a tidy format
df <- merge(dfOutsideTemp, dfHeatEnergy, by = "day")

# calculate season
df <- df %>% mutate(season = redutils::season(df$day))

# static chart with ggplot
p <- ggplot2::ggplot(df) +
  ggplot2::geom_point(aes(x = tempMean,
                          y = energyCons,
                          color = season,
                          alpha = 0.1,
                          text = paste("</br>Date:  ", as.Date(df$day),
                                       "</br>Temp: ", round(df$tempMean, digits = 1), "\u00B0C",
                                       "</br>Energy: ", round(df$energyCons, digits = 0), "kWh/d",
                                       "</br>Season: ", df$season))
                      ) +
  scale_color_manual(values=c("#440154", "#2db27d", "#fde725", "#365c8d")) +
  ggtitle("Building Energy Signature") +
        theme_minimal() +
        theme(
          legend.position="none",
          plot.title = element_text(hjust = 0.5)
        )

# interactive chart
plotly::ggplotly(p, tooltip = c("text")) %>%
  layout(xaxis = list(title = "Outside temperature (\u00B0C)",
                      range = c(min(-5,min(df$tempMean)), max(35,max(df$tempMean))), zeroline = F),
         yaxis = list(title = "Daily energy consumption (kWh/d)",
                      range = c(-5, max(df$energyCons) + 10)),
         showlegend = TRUE
         ) %>%
  plotly::config(displayModeBar = FALSE, displaylogo = FALSE)

6.3 Daily Energy Profiles

## [1] "English_United States.1252"
library(plotly)
library(dplyr)
library(lubridate)

# load time series data
df <- read.csv("https://github.com/hslu-ige-laes/edar/raw/master/sampleData/eboBookEleMeter.csv",
               stringsAsFactors=FALSE,
               sep =";")

# rename column names
colnames(df) <- c("timestamp", "meterValue")

df$timestamp <- parse_date_time(df$timestamp,
                                orders = "YmdHMS",
                                tz = "Europe/Zurich")
df$timestamp <- force_tz(df$timestamp, tzone = "UTC")

# uncomment to filter time range if necessary
#df <- df %>% filter(timestamp > "2015-03-01 00:00:00", timestamp < "2015-04-01 00:00:00")

# Fill missing values with NA
grid.df <- data.frame(timestamp = seq(df[1, 1], df[nrow(df), 1], by = "15 mins"))
df <- merge(df, grid.df, all = TRUE)

# convert steadily counting energy meter value from kWh to power in kW
df <- df %>% 
  mutate(value = (meterValue - lag(meterValue))*4) %>%
  select(-meterValue) %>%
  na.omit()

# remove negative values which occur beause of change summer/winter time
df <- df %>% filter(value >= 0)

# add metadata for later grouping and visualization purposes
df$x <- hour(df$timestamp) + minute(df$timestamp)/60 + second(df$timestamp) / 3600
df$weekday <- weekdays(df$timestamp)
df$weekday <- factor(df$weekday, c("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday", "Sunday"))
df$day <- as.Date(df$timestamp, format = "%Y-%m-%d  %H:%M:%S")

df <- df %>% mutate(value = ifelse(x == 0.00, NA, df$value))

# plot graph with all time series
rangeX <- seq(0,24,0.25)
maxValue <- max(df$value, na.rm = TRUE)*1.05

df %>% 
  highlight_key(~day) %>%
  plot_ly(x=~x,
          y=~value,
          color=~weekday,
          type="scatter",
          mode="lines",
          line = list(width = 1),
          alpha = 0.15,
          colors = "dodgerblue4",
          text = ~day,
          hovertemplate = paste("Time: ", format(df$timestamp, "%H:%M"),
                                "<br>Date: ", format(df$timestamp, "%Y-%m-%d"),
                                "<br>Value: %{y:.0f}")) %>%
  # workaround with add_trace to have fixed y axis when selecting a dedicated day
  add_trace(x = 0, y = 0, type = "scatter", showlegend = FALSE, opacity=0) %>%
  add_trace(x = 24, y = maxValue, type = "scatter", showlegend = FALSE, opacity=0) %>%
  layout(title = "Superimposed Profiles of Power Consumption per 15 min",
         showlegend = TRUE,
         xaxis = list(
           title = "Hour of day",
           range = rangeX,
           tickvals = list(0, 3, 6, 9, 12, 15, 18, 21),
           showline=TRUE
           ),
           yaxis = list(
             title = "Power (kW)",
             range = c(0, maxValue)
           )
         ) %>% 
  highlight(on = "plotly_hover",
            off = "plotly_doubleclick",
            color = "orange",
            opacityDim = 1.0,
            selected = attrs_selected(showlegend = FALSE)) %>% # this hides elements in the legend
  plotly::config(modeBarButtons = list(list("toImage")), displaylogo = FALSE)

Next we want to create an overview with the mean values for each 15 minute slot per day.

Append the following code at the end of your script:

6.4 Mollier hx Diagram

6.4.1 Task

You want to plot a mollier h-x diagram with

  • scatter plot of temperature- and humidity sensor data (mean values per day)

  • points colored according to season

  • comfort zone

6.4.2 Basis

  • A csv file with time series from multiple temperature and humidity sensors in °C and %rH

6.4.3 Solution

The sensor data is not in a constant intervall and not yet aggregated. So after reading in the time series the data has to get filtered and aggregated per day.

Finally use the plot function mollierHxDiagram from the redutils package (R Energy Data Utilities). If you have not yet installed this package, proceed as follows:

Create a new script, copy/paste the following code and run it:

6.4.4 Discussion

The diagram is based on D3 and packaged into the package redutils. The original D3 source with a html integration you can find here: https://github.com/hslu-ige-laes/d3-mollierhx

6.4.5 See Also

If your two time series are in separate files, you must first read them in separately and then merge them into one data frame. See chapter 4.2.4